package main

import (
	"crypto/md5"
	"ecld.com/prowatcher/pb"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"net"
	"regexp"
	"strings"
	"time"
)

type authHandler struct {
	store Store
}

func NewAuthHandler() *authHandler {
	handler := &authHandler{}
	store := NewMySQLStore()
	store.Register(handler)
	logger.Println("Build Auth Handler")
	return handler
}

func (h *authHandler) Store() Store {
	return h.store
}

func (h *authHandler) SetStore(store Store) {
	h.store = store
}

func (h *authHandler) Login(uname string, inputPasswd string) error {

	if len(inputPasswd) < 6 {
		return fmt.Errorf(PASSWD_TOO_SHORT)
	}
	passwd, err := h.Store().QueryPasswd(uname)
	if err != nil {
		return err
	}

	logger.Printf("uname = %s passwd = %s", uname, passwd)

	if passwd == "" {
		return fmt.Errorf(USER_NOT_EXIST)
	}
	if passwd != touchMD5(inputPasswd) {
		return fmt.Errorf(PASSWD_INCORRECT)
	}
	logger.Println("login in")
	return nil
}

func (h *authHandler) LoginByToken(uname string, utoken string) (int64, error) {

	dyn, err := h.Store().QueryToken(uname)
	if err != nil {
		return 0, err
	}
	token, expireTime := dyn.token, dyn.valid_until
	if err != nil {
		return 0, err
	}
	if token == "" {
		return 0, fmt.Errorf(TOKEN_NOT_EXIST)
	}
	//if token correct
	if utoken != token {
		return 0, fmt.Errorf(TOKEN_INCORRECT)
	}
	//if token not expired
	if expireTime < time.Now().Unix()*1000 {
		return 0, fmt.Errorf(TOKEN_EXPIED)
	}
	logger.Println("identity recognition")
	return expireTime, nil

}

func (h *authHandler) UpdateInfo(uname string, passwd string) error {

	err := h.Store().UpdatePasswd(uname, touchMD5(passwd))
	if nil != err {
		return err
	}
	return nil

}

func (h *authHandler) TouchToken(uname string) (pb.Token, int64, error) {

	dyn, err := h.Store().QueryToken(uname)
	if err != nil {
		return pb.Token{}, 0, err
	}
	token, expireTime := dyn.token, dyn.valid_until
	current := time.Now().Unix() * 1000
	//if token expired, generate new token
	if expireTime < current {
		token = touchMD5(uname + fmt.Sprint(current))
		expireTime = current + tokenRenewTime
		err := h.Store().UpdateToken(
			uname,
			&dynToken{
				uname:       uname,
				token:       token,
				valid_until: expireTime,
			},
		)
		if err != nil {
			return pb.Token{}, 0, err
		}
	}
	logger.Println("touch token")
	return pb.Token{User: uname, Token: token}, expireTime, nil

}

func (h *authHandler) TouchPermission(uname string, resourcePath string) error {

	policies, err := h.Store().QueryResourcePath(uname)
	if err != nil {
		return err
	}
	if len(policies) == 0 {
		return fmt.Errorf(PERMISSION_DENIED)
	}
	targetNs, targetAddrs, targetAction := parser(resourcePath)
	for policy, recursive := range policies {
		ns, addrs, action := parser(policy)
		//check resource
		if !(audit(targetNs, ns, recursive) || audit(targetAddrs, addrs, recursive)) {
			continue
		}
		//check action
		if action == targetAction {
			logger.Println("audit pass")
			return nil
		}
	}
	return fmt.Errorf(PERMISSION_DENIED)

}

func audit(path string, pattern string, recursive bool) bool {

	if path == "" {
		return false
	}
	r, _ := regexp.Compile("\\*")
	pattern = r.ReplaceAllString(pattern, ".*")
	if recursive {
		pattern += "(/.*)*"
	} else {
		pattern += "$"
	}
	match, _ := regexp.MatchString(pattern, path)

	return match
}

//interprets addresses into domain names, or the opposite.
func parser(resourcePath string) (ns, addrs, action string) {
	resourcePath = strings.ToLower(resourcePath)
	rpSplit := strings.Split(resourcePath, `?`)
	for _, p := range strings.Split(rpSplit[1], `&`) {
		if strings.Contains(p, `action`) {
			paramSplit := strings.Split(p, `=`)
			action = paramSplit[1]
			break
		}
	}
	path := strings.Split(rpSplit[0], `/`)
	if path[1] == "" {
		return "","",""
	}
	if isIPAddress(path[1]) {
		addrs = strings.Join(path, `/`)
		tmp, err := net.LookupHost(addrs)
		if err == nil {
			path[1] = tmp[0]
			ns = strings.Join(path, `/`)
		}
	} else {
		ns = strings.Join(path, `/`)
		tmp, err := net.LookupAddr(ns)
		if err == nil {
			path[1] = tmp[0]
			addrs = strings.Join(path, `/`)
		}
	}
	return
}

func isIPAddress(host string) bool {

	part := strings.Split(host, `.`)
	if len(part) != 4 {
		return false
	}
	for _, pa := range part {
		match, _ := regexp.MatchString("[0-9]*", pa)
		if !match {
			return false
		}
	}
	return true
}

func touchMD5(str string) string {
	data := []byte(str + code)
	has := md5.Sum(data)
	md5str := fmt.Sprintf("%X", has)
	return md5str

}
